// 10.3 定制操作
/**
 * 很多算法都会比较输入序列中的元素。默认情况下，这类算法使用元素类型的<或==运算符完成比较。
 * 标准库还为这些算法定义了额外的版本，允许我们提供自己定义的操作来代替默认运算符。
 * 例如，sort算法默认使用元素类型的<运算符。但可能我们希望的排序顺序与<所定义的顺序不同，
 * 或是我们的序列可能保存的是未定义<运算符的元素类型（如Sales_data）。在这两种情况下，都需要重载sort的默认行为。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
#include <functional>
#include <ostream>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
//#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy, std::sort, std::unique, std::stable_sort, std::find_if, std::for_each;
using std::transform, std::bind, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3;
using std::ostream,std::ref;

void elimDups(vector<string> &words);
bool isShorter(const string &s1, const string &s2);
void biggies(vector<string> &words, vector<string>::size_type sz);
string make_plural(std::ptrdiff_t count, string a, string b);
void fcn1();
void fcn2();
void fcn3();
void fcn4();
bool check_size(const string &s, string::size_type sz);
ostream &print(ostream &os, const string &s, char c);

int main()
{
    // 10.3.4 参数绑定
    // 对于那种只在一两个地方使用的简单操作，lambda表达式是最有用的。
    // 如果我们需要在很多地方使用相同的操作，通常应该定义一个函数，而不是多次编写相同的lambda表达式。
    // 如果lambda的捕获列表为空，通常可以用函数来代替它。
    // 如前面章节所示，既可以用一个lambda，也可以用函数isShorter来实现将vector中的单词按长度排序。
    // 但是，对于捕获局部变量的lambda，用函数来替换它就不是那么容易了，例如在find_if中调用check_size这个拥有两个形参的函数。
    // 如前文所示，find_if接受一个一元谓词，因此传递给find_if的可调用对象必须接受单一参数。

    // 标准库bind函数
    // 当某个泛型算法（例如find_if）需要一个一元谓词（拥有一个参数的可调用的表达式且返回结果可转换为bool类型），
    // 并且我们的函数拥有多个参数的时候，可以使用定义在头文件functional中的bind标准库函数。
    // 调用bind的一般形式：auto newCallable = bind(callable, arg_list);
    // 调用bind的一般形式为：[插图]其中，newCallable本身是一个可调用对象，arg_list是一个逗号分隔的参数列表，对应给定的callable的参数。
    // 即，当我们调用newCallable时，newCallable会调用callable，并传递给它arg_list中的参数。
    // arg_list中的参数可能包含形如_n的名字，其中n是一个整数。这些参数是“占位符”，表示newCallable的参数，它们占据了传递给newCallable的参数的“位置”。
    // 数值n表示生成的可调用对象中参数的位置：_1为newCallable的第一个参数，_2为第二个参数，依此类推。

    // 绑定check_size的sz参数
    // 此bind调用只有一个占位符，表示check6只接受单一参数。
    auto check6 = bind(check_size, _1, 6); // _1在std::placeholders命名空间下，如：std::placeholders::_1
    string s = "hello";
    bool b1 = check6(s); // check6(s)会调用check_size(s,6);

    // 使用placeholders名字
    // 名字_n都定义在一个名为placeholders的命名空间中，而这个命名空间本身定义在std命名空间（参见3.1节，第74页）中。
    // 为了使用这些名字，两个命名空间都要写上。例如：using std::placeholders::_1;
    // 对每个占位符名字，我们都必须提供一个单独的using声明。编写这样的声明很烦人，也很容易出错。
    // 可以使用另外一种不同形式的using语句（详细内容将在18.2.2节（第702页）中介绍），而不是分别声明每个占位符，
    // 如下所示：[插图]
    // using namespace namespace_name;
    // 这种形式说明希望所有来自namespace_name的名字都可以在我们的程序中直接使用。例如：[插图]
    using namespace std::placeholders;

    // bind的参数
    // 可以用bind绑定给定可调用对象中的参数或重新安排其顺序。
    // 例如，假定f是一个可调用对象，它有5个参数，则下面对bind的调用：[插图]
    // auto g = bind(f,a,b,_2,c,_1);
    // 生成一个新的可调用对象，它有两个参数，分别用占位符_2和_1表示。
    // 这个新的可调用对象将它自己的参数作为第三个和第五个参数传递给f。f的第一个、第二个和第四个参数分别被绑定到给定的值a、b和c上。

    // 用bind重排参数顺序
    // 下面是用bind重排参数顺序的一个具体例子，我们可以用bind颠倒isShroter的含义：[插图]
    vector<string> words{"abc", "def", "abcd", "defg", "hihi", "hahahaha"};
    sort(words.begin(), words.end(), isShorter);               // 按单词长度由短至长排序
    sort(words.begin(), words.end(), bind(isShorter, _2, _1)); // 按单词长度由长至短排序

    // 绑定引用参数
    // 默认情况下，bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。
    // 但是，与lambda类似，有时对有些绑定的参数我们希望以引用方式传递，或是要绑定参数的类型无法拷贝。
    // 如果我们希望传递给bind一个对象而又不拷贝它，就必须使用标准库ref函数：[插图]
    ostream& os(cout);
    for_each(words.begin(), words.end(), bind(print,ref(os),_1,' '));
    // 标准库中还有一个cref函数，生成一个保存const引用的类。与bind一样，函数ref和cref也定义在头文件functional中。

    // 向后兼容：参数绑定
    // 旧版本C++提供的绑定函数参数的语言特性限制更多，也更复杂。
    // 标准库定义了两个分别名为bind1st和bind2nd的函数。
    // 类似bind，这两个函数接受一个函数作为参数，生成一个新的可调用对象，该对象调用给定函数，并将绑定的参数传递给它。
    // 但是，这些函数分别只能绑定第一个或第二个参数。由于这些函数局限太强，在新标准中已被弃用（deprecated）。
    // 所谓被弃用的特性就是在新版本中不再支持的特性。新的C++程序应该使用bind。

    return 0;
}

void elimDups(vector<string> &words)
{
    // 按字典排序words，以便查找重复单词
    sort(words.begin(), words.end());
    // unique重排输入范围，使得每个单词只出现一次
    // 排列在范围的前部，返回指向不重复区域之后一个位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    // 使用向量操作erase删除重复单词
    words.erase(end_unique, words.end());
}

// 比较函数，用来按长度排序单词
bool isShorter(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

// 求大于等于一个给定长度的单词有多少。我们还会修改输出，使程序只打印大于等于给定长度的单词。
void biggies(vector<string> &words, vector<string>::size_type sz)
{
    elimDups(words); // 将words按字典排序并删除重复单词
    // 按长度排序，长度相同的单词维持字典序
    stable_sort(words.begin(), words.end(), isShorter);
    // 获取一个迭代器，指向第一个满足size() > sz的元素
    auto wc = find_if(words.begin(), words.end(), [sz](const string &a)
                      { return a.size() >= sz; });
    // 计算满足size() > sz的元素的数目
    auto count = words.end() - wc;
    cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl;
    // 打印长度大于等于给定值的单词，每个单词后面接一个空格
    for_each(wc, words.end(), [](const string &s)
             { cout << s << " "; });
    cout << endl;
}

string make_plural(std::ptrdiff_t count, string a, string b)
{
    if (count > 1)
        return a + b;
    else
        return a;
}

void fcn1()
{
    size_t v1 = 42; // 局部变量
    // 将v1拷贝到名为f的可调用对象
    auto f = [v1]
    { return v1; };
    v1 = 0;
    auto j = f(); // j为42；f保存了我们创建它时v1的拷贝
}

void fcn2()
{
    size_t v1 = 42; // 局部变量
    // 对象f2包含v1的引用
    auto f2 = [&v1]
    { return v1; };
    v1 = 0;
    auto j = f2(); // j为0；f保存了v1的引用，而非拷贝
}

void fcn3()
{
    size_t v1 = 42; // 局部变量
    // f可以改变它所捕获的变量的值
    auto f = [v1]() mutable
    { return ++v1; };
    v1 = 0;
    auto j = f(); // j为43
}

void fcn4()
{
    size_t v1 = 42; // 局部变量
    // v1是一个非const变量的引用
    // 可以通过f2中的引用来改变它
    auto f2 = [&v1]() mutable
    { return ++v1; };
    v1 = 0;
    auto j = f2(); // j为1
}

bool check_size(const string &s, string::size_type sz)
{
    return s.size() >= sz;
}

ostream &print(ostream &os, const string &s, char c)
{
    return os << s << c;
}